package com.tuniu.agents.common.handler;

import com.tuniu.agents.advisor.ChatEnvironmentMemory;
import com.tuniu.agents.common.command.CommandManagerUtil;
import com.tuniu.agents.common.command.action.DefaultCallAgentAction;
import com.tuniu.agents.common.command.action.DefaultOutputTextAction;
import com.tuniu.agents.common.command.match.StringMatch;
import com.tuniu.agents.common.entity.itinerary.BaseItinerary;
import com.tuniu.agents.common.entity.itinerary.Resource;
import com.tuniu.agents.common.entity.itinerary.enums.ResourceStatus;
import com.tuniu.agents.common.entity.itinerary.resource.FlightResource;
import com.tuniu.agents.common.i18n.I18nConstants;
import com.tuniu.agents.common.message.BaseAgentNewResp;
import com.tuniu.agents.common.message.CardTypeEnum;
import com.tuniu.agents.common.service.itinerary.ItineraryService;
import com.tuniu.agents.common.util.NumberingUtil;
import com.tuniu.agents.impl.flight.FlightAgentConfig;
import com.tuniu.agents.memory.MemoryManager;
import com.tuniu.agents.message.MessageRender;
import com.tuniu.agents.tools.flights.domestic.oneway.FlightInformationQueryTool;
import com.tuniu.agents.tools.flights.domestic.oneway.FlightLowPriceQueryTool;
import com.tuniu.agents.tools.flights.domestic.oneway.FlightTimeQueryTool;
import com.tuniu.agents.tools.flights.domestic.oneway.FlightTransferQueryTool;
import com.tuniu.agents.tools.flights.domestic.roundtrip.RoundFlightInformationQueryTool;
import com.tuniu.agents.tools.flights.domestic.roundtrip.RoundFlightLowPriceQueryTool;
import com.tuniu.agents.tools.flights.international.oneway.IntlFlightInformationQueryTool;
import com.tuniu.agents.tools.flights.international.oneway.IntlFlightLowPriceQueryTool;
import com.tuniu.agents.tools.flights.international.oneway.IntlFlightTransferQueryTool;
import com.tuniu.agents.tools.flights.international.roundtrip.IntlRoundFlightInformationQueryTool;
import com.tuniu.agents.tools.flights.international.roundtrip.IntlRoundFlightLowPriceQueryTool;
import com.tuniu.agents.util.I18nUtil;
import com.tuniu.agents.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.groovy.util.Maps;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.math.BigDecimal;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * Implementation of Air Ticket Streaming Response Processor
 * Supports Both Recommendation and Suggestion Modes
 */
@Slf4j
public class FlightStreamResponseHandler implements IStreamResponseHandler {


    private final String questionId;

    private final String toolName;
    private final String agentId;
    private final ChatEnvironmentMemory chatEnvironmentMemory;
    private final MemoryManager memoryManager;

    // Matching Mode
    private final Pattern bracketPattern;

    private final Pattern angleBracketPattern=Pattern.compile("<([^>]+)>");

    public static final String MESSAGE = "message";
    public static final String CONVERSATION_ID = "conversationId";

    public static final String DATE_FORMAT = "yyyy-MM-dd";

    // Match flight number pattern starting with letters followed by digits
    Pattern flightNumberPattern = Pattern.compile("^([A-Za-z]\\d+)");

    private MessageRender messageRender;

    private ItineraryService itineraryService;

    public FlightStreamResponseHandler(String toolName, String agentId, ChatEnvironmentMemory chatEnvironmentMemory, MemoryManager memoryManager, MessageRender messageRender, ItineraryService itineraryService) {
        this("", "《([^》]+)》",toolName, agentId,chatEnvironmentMemory, memoryManager, messageRender, itineraryService);
    }

    public FlightStreamResponseHandler(String questionId, String itemRegex, String toolName, String agentId, ChatEnvironmentMemory chatEnvironmentMemory, MemoryManager memoryManager, MessageRender messageRender, ItineraryService itineraryService) {
        if(!StringUtils.hasText(itemRegex)) itemRegex = "《([^》]+)》";

        this.bracketPattern = Pattern.compile(itemRegex);
        this.questionId = questionId;
        this.toolName = toolName;
        this.agentId = agentId;
        this.chatEnvironmentMemory = chatEnvironmentMemory;
        this.memoryManager = memoryManager;
        this.messageRender = messageRender;
        this.itineraryService = itineraryService;
    }


    @Override
    public Flux<String> handle(Map<String, Object> handlerContext, Flux<String> flux) {

        String message = (String) handlerContext.get(MESSAGE);
        String conversationId = (String) handlerContext.get(CONVERSATION_ID);

        Map<String, Object> toolResponseMap = getToolResponseMap(message,toolName,handlerContext);
        String cardType = getCardTypeByToolName(toolName).getCardType();
        List<String> matchedKeys = new ArrayList<>();
        // Create a processing stream with cumulative buffering
        StringBuilder buffer = new StringBuilder();
        StringBuilder recommendFlightsMarkdown = new StringBuilder();
        AtomicInteger index = new AtomicInteger(1);
        return flux.flatMap(chunk -> {
            if (!StringUtils.hasText(chunk)) {
                return Flux.empty();
            }

            // Accumulated content
            buffer.append(chunk);
            String content = buffer.toString();

            // First, try matching the '《》' pattern
            Matcher matcher = bracketPattern.matcher(content);
            Flux<String> response = processMatches(matcher, buffer, toolResponseMap, cardType, matchedKeys, message, recommendFlightsMarkdown,conversationId, index);
            if (response != null) {
                return response;
            }

            // If there is no match for '《》', try matching the<>pattern
            matcher = angleBracketPattern.matcher(content);
            response = processMatches(matcher, buffer, toolResponseMap, cardType, matchedKeys, message, recommendFlightsMarkdown,conversationId, index);
            if (response != null) {
                return response;
            }

            return Flux.empty();
        }).concatWith(Mono.fromCallable(() -> {
            String remainingContent = buffer.toString();

            //Store the recommendation results in markdown format in the context
            if(!recommendFlightsMarkdown.isEmpty()) {
                recommendFlightsMarkdown.append("\n");  // The second line break at the end of 'markdown'
                if(StringUtils.hasText(remainingContent)){
                    recommendFlightsMarkdown.append(remainingContent.trim());
                }
                memoryManager.setMessageRight(conversationId, Collections.singletonList(new AssistantMessage(recommendFlightsMarkdown.toString(), Map.of("agentId", agentId))));

            }
            return StringUtils.hasText(remainingContent) ?
                    BaseAgentNewResp.sendAnswerMsg(questionId, remainingContent) : "";//Return an empty string instead of null
        }).filter(StringUtils::hasText));
    }

    /**
     * Handle the matching result
     * @return Returns the response string if the matching is successful; otherwise, returns null.
     */
    private Flux<String> processMatches(Matcher matcher, StringBuilder buffer, Map<String, Object> toolResponseMap,
                                  String cardType, List<String> matchedKeys, String message, StringBuilder recommendFlightsMarkdown, String conversationId, AtomicInteger index) {
        if (matcher.find()) {
            String key = extractKey(matcher);

          /*  Matcher flightNumMatcher = flightNumPattern.matcher(key);
            while (flightNumMatcher.find()) {
                key = extractKey(flightNumMatcher);
            }*/

            int endIndex = matcher.start();
            String reason = extractReason(buffer.toString(), endIndex);

            matchedKeys.add(key);

            // Update the buffer and remove the processed part
            buffer.delete(0, matcher.end());

            if (toolResponseMap == null) {
                log.error("toolResponseMap为null，message:{}", message);
                return Flux.just(BaseAgentNewResp.sendErrorMsg("toolResponseMap为null", questionId));
            }

            Object data = toolResponseMap.get(key);
            if (data == null) {
                log.error("toolResponseMap.get(key)为null，key:{},toolResponseMap:{}", key, JsonUtil.toJson(toolResponseMap));
                return Flux.just(BaseAgentNewResp.sendErrorMsg("toolResponseMap.get(key)为null，key:" + key, questionId));
            }
            String saveInMdReason = StringUtils.hasText(reason) && reason.contains("：") ? reason.substring(reason.indexOf("：") + 1) : reason;

            // markdown message
            String markdownResponse = null;
            String viewMarkdownResponse = null;
            Map<String, Object> flightDataMap = JsonUtil.toMap(JsonUtil.toJson(data));
            flightDataMap.put("recommendationReason",saveInMdReason);
            String acceptLanguage = I18nUtil.getLocaleLanguageTag(conversationId);
            if (CardTypeEnum.FLIGHT_LIST_ONE.getCardType().equals(cardType)) {
                markdownResponse = messageRender.renderTemplate("flight/flight-list-one.ftlh", Map.of(I18nConstants.LANGUAGE_TAG, acceptLanguage), flightDataMap);
                // Save Command
                long indexNumber = NumberingUtil.incrWithExpire(conversationId);
                viewMarkdownResponse = NumberingUtil.getNumberMarkdown(markdownResponse, "#### ", indexNumber);
                CommandManagerUtil.saveListCommand(conversationId, cardType + "+" + indexNumber, "stringMatch" , "defaultCallAgentAction",
                        Maps.of(DefaultCallAgentAction.PARAM_AGENT_ID, FlightAgentConfig.FLIGHT_TICKET_AGENT_ID, DefaultOutputTextAction.KEY_MESSAGE, key, StringMatch.KEY_STRING, List.of(String.valueOf(indexNumber), key)));
            } else if (CardTypeEnum.FLIGHT_LIST_ROUND.getCardType().equals(cardType)){
                long indexNumber = NumberingUtil.incrWithExpire(conversationId);
                markdownResponse = messageRender.renderTemplate("flight/flight-list-round.ftlh", Map.of(I18nConstants.LANGUAGE_TAG, acceptLanguage), flightDataMap);
                // Save Command
                viewMarkdownResponse = NumberingUtil.getNumberMarkdown(markdownResponse, "#### ", indexNumber);
                CommandManagerUtil.saveListCommand(conversationId, cardType + "+" + indexNumber, "stringMatch" , "defaultCallAgentAction",
                        Maps.of(DefaultCallAgentAction.PARAM_AGENT_ID, FlightAgentConfig.FLIGHT_TICKET_AGENT_ID, DefaultOutputTextAction.KEY_MESSAGE, key, StringMatch.KEY_STRING, List.of(String.valueOf(indexNumber), key)));
            }
            // Create a Markdown list of recommended flights and store it in the context
            saveRecommendFlights(data, saveInMdReason, cardType, recommendFlightsMarkdown);

            if (matchedKeys.size() == 1) {
                BaseItinerary flightItinerary = itineraryService.getItinerary(conversationId, null);
                if(flightItinerary != null) {
                    Resource flightResource = null;
                    if( data instanceof FlightLowPriceQueryTool.FlightItem flightItem) {
                        flightResource = new FlightResource(flightItem.airlineCompany(),flightItem.departureCityName(),flightItem.arrivalCityName(),flightItem.departureTime(),JsonUtil.toJson(data),markdownResponse, new BigDecimal(flightItem.priceWithTax()), ResourceStatus.PENDING);
                    }else if(data instanceof RoundFlightLowPriceQueryTool.FlightInfo flightInfo){
                        flightResource = new FlightResource(flightInfo.flightInfo().get(0).airlineCompany(),flightInfo.flightInfo().get(0).departureCityName(),flightInfo.flightInfo().get(0).arrivalCityName(),flightInfo.flightInfo().get(0).departureTime(),JsonUtil.toJson(data),markdownResponse,new BigDecimal(flightInfo.priceInfo()), ResourceStatus.PENDING);
                    }
                    if(flightResource != null) {
                        itineraryService.saveResource(conversationId, null, flightResource);
                    }
                }
            }

            //Card message
            String response = createItemResponse(data, viewMarkdownResponse, cardType);

            log.info("flightStreamResponseHandler.handle:{},response:{}", message, response);
            return Flux.just(response);
        }
        return null;
    }

    /**
     * Extract the key from the match
     */
    private String extractKey(Matcher matcher) {
        String fullMatch = matcher.group(1).trim();
        Matcher flightNumberMatcher = flightNumberPattern.matcher(fullMatch);
        if (flightNumberMatcher.find()) {
            return flightNumberMatcher.group(1);
        }
        //If no specific pattern is matched, return the original string
        return fullMatch;
    }

    /**
     * Extract the reason
     */
    private String extractReason(String content, int endIndex) {
        return content.substring(0, endIndex).trim();
    }

    /**
     * Create a project response
     */
    private String createItemResponse(Object data, String reason, String cardType) {

        return BaseAgentNewResp.sendCardMsg(
                cardType,
                Collections.singletonList(data),
                reason,
                questionId
        );
    }

    private void saveRecommendFlights(Object data, String reason, String cardType, StringBuilder recommendFlightsMarkdown) {
        if (CardTypeEnum.FLIGHT_LIST_ONE.getCardType().equals(cardType)) {
            // Build a Markdown list of one-way flights
            FlightLowPriceQueryTool.FlightItem flightItem = (FlightLowPriceQueryTool.FlightItem) data;
            if (recommendFlightsMarkdown.isEmpty()) {
                recommendFlightsMarkdown.append("| Flight Key | Flight Number | Departure Time | Arrival Time | Departure Airport | Arrival Airport | Adult Price | Reasons for Recommendation |\n");
                recommendFlightsMarkdown.append("| --- | --- | --- | --- | --- | --- | --- | --- |\n");
            }

            recommendFlightsMarkdown.append("|").append(flightItem.flightNumber())
                    .append("|").append(flightItem.flightNumber())
                    .append("|").append(flightItem.departureTime())
                    .append("|").append(flightItem.arrivalTime())
                    .append("|").append(flightItem.departureAirport())
                    .append("Airport|").append(flightItem.arrivalAirport())
                    .append("Airport|").append(flightItem.priceWithTax())
                    .append("|").append(reason)
                    .append("|\n");
        } else if (CardTypeEnum.FLIGHT_LIST_ROUND.getCardType().equals(cardType)) {
            // Build a Markdown list of round-trip flights
            RoundFlightLowPriceQueryTool.FlightInfo flightInfo = (RoundFlightLowPriceQueryTool.FlightInfo) data;
            if (recommendFlightsMarkdown.isEmpty()) {
                recommendFlightsMarkdown.append("| Flight Key | Adult Price | Reasons for Recommendation ");
                recommendFlightsMarkdown.append("| Outbound Flight Number | Outbound Departure Time | Outbound Arrival Time | Outbound Departure Airport | Outbound Arrival Airport ");
                recommendFlightsMarkdown.append("| Return Flight Number | Return Departure Time | Return Arrival Time | Return Departure Airport | Return Arrival Airport |\n");
                recommendFlightsMarkdown.append("| --- | --- | ---");
                recommendFlightsMarkdown.append("| --- | --- | --- | --- | --- ");
                recommendFlightsMarkdown.append("| --- | --- | --- | --- | --- |\n");
            }

            RoundFlightLowPriceQueryTool.FlightItem goFlightItem = flightInfo.flightInfo().get(0);
            RoundFlightLowPriceQueryTool.FlightItem backFlightItem = flightInfo.flightInfo().get(1);

            recommendFlightsMarkdown.append("|").append(flightInfo.flightKey())
                                    .append("|").append(flightInfo.priceWithTax())
                                    .append("|").append(reason);

            // Outbound flight information
            recommendFlightsMarkdown.append("|").append(goFlightItem.flightNumber())
                                    .append("|").append(goFlightItem.departureTime())
                                    .append("|").append(goFlightItem.arrivalTime())
                                    .append("|").append(goFlightItem.departureAirport())
                                    .append("|").append(goFlightItem.arrivalAirport());

            // Return flight information
            recommendFlightsMarkdown.append("|").append(backFlightItem.flightNumber())
                                    .append("|").append(backFlightItem.departureTime())
                                    .append("|").append(backFlightItem.arrivalTime())
                                    .append("|").append(backFlightItem.departureAirport())
                                    .append("|").append(backFlightItem.arrivalAirport());

            recommendFlightsMarkdown.append("|\n");
        }
    }


    private Map<String, Object> getToolResponseMap(String message,String toolName,Map<String, Object> handlerContext) {
        switch (toolName) {
            case FlightLowPriceQueryTool.TOOLNAME, FlightTransferQueryTool.TOOLNAME:
                return FlightLowPriceQueryTool.buildCard(message, chatEnvironmentMemory.get(handlerContext.get("conversationId").toString()));
            case RoundFlightLowPriceQueryTool.TOOLNAME:
                return RoundFlightLowPriceQueryTool.buildCard(message, chatEnvironmentMemory.get(handlerContext.get("conversationId").toString()));
//                return JsonUtil.fromJson(message, RoundFlightLowPriceQueryTool.Response.class).data()
//                        .stream()
//                        .collect(Collectors.toMap(RoundFlightLowPriceQueryTool.FlightInfo::flightKey, flight -> flight, (k1, k2) -> k2
//                        ));
            default:
                return null;
        }
    }

    private CardTypeEnum getCardTypeByToolName(String toolName) {
        switch (toolName) {
            case FlightLowPriceQueryTool.TOOLNAME, FlightTransferQueryTool.TOOLNAME, FlightTimeQueryTool.TOOLNAME,
                 IntlFlightLowPriceQueryTool.TOOLNAME, IntlFlightTransferQueryTool.TOOLNAME:
                return CardTypeEnum.FLIGHT_LIST_ONE;
            case RoundFlightLowPriceQueryTool.TOOLNAME, IntlRoundFlightLowPriceQueryTool.TOOLNAME:
                return CardTypeEnum.FLIGHT_LIST_ROUND;
            case FlightInformationQueryTool.TOOLNAME, IntlFlightInformationQueryTool.TOOLNAME:
                return CardTypeEnum.FLIGHT_DETAIL;
            case RoundFlightInformationQueryTool.TOOLNAME, IntlRoundFlightInformationQueryTool.TOOLNAME:
                return CardTypeEnum.FLIGHT_ROUND_DETAIL;
            default:
                return CardTypeEnum.FLIGHT_LIST_ONE;
        }
    }

    public static String formatDate(Date date) {
        if (date == null) {
            return null;
        }
        return date.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDateTime()
                .format(DateTimeFormatter.ofPattern(DATE_FORMAT));
    }
}